package com.luculent.base;

import java.io.File;
import java.io.IOException;
import java.util.List;
import java.util.Timer;
import java.util.TimerTask;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;

import javax.annotation.PostConstruct;

import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.tuple.Pair;
import org.openqa.selenium.By;
import org.openqa.selenium.JavascriptExecutor;
import org.openqa.selenium.Proxy;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.chrome.ChromeDriver;
import org.openqa.selenium.chrome.ChromeDriverService;
import org.openqa.selenium.chrome.ChromeOptions;
import org.openqa.selenium.remote.CapabilityType;

import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Lists;
import com.luculent.utils.WorkId;

import io.netty.handler.codec.http.DefaultFullHttpResponse;
import io.netty.handler.codec.http.HttpRequest;
import io.netty.handler.codec.http.HttpResponse;
import io.netty.handler.codec.http.HttpResponseStatus;
import io.netty.util.ResourceLeakDetector;
import lombok.extern.slf4j.Slf4j;
import net.lightbody.bmp.BrowserMobProxy;
import net.lightbody.bmp.BrowserMobProxyServer;
import net.lightbody.bmp.client.ClientUtil;
import net.lightbody.bmp.filters.RequestFilter;
import net.lightbody.bmp.filters.ResponseFilter;
import net.lightbody.bmp.filters.ResponseFilterAdapter;
import net.lightbody.bmp.proxy.BlacklistEntry;
import net.lightbody.bmp.proxy.CaptureType;
import net.lightbody.bmp.util.HttpMessageContents;
import net.lightbody.bmp.util.HttpMessageInfo;
@Slf4j
public class FPBDriverProxy {
	
	public WebDriver driver = null; 
	private BrowserMobProxy proxy = new BrowserMobProxyServer();  
    private ChromeDriverService chromeService = null;
    private String driverPath = "F:/face/chromedriver.exe"; 
    
    public final ConcurrentHashMap<String, String> responses = new ConcurrentHashMap<String, String>();
    public final LinkedBlockingQueue<Pair<String,ImmutableMap<String,Object>>> requsets = new LinkedBlockingQueue<Pair<String,ImmutableMap<String,Object>>>();
    
    private ProxyRequestFilter reqFilter = new ProxyRequestFilter(requsets);  
    public ReentrantReadWriteLock requsetLock = new ReentrantReadWriteLock(); 
    
    private final ReentrantLock takeLock = new ReentrantLock(); 
    private final Condition notEmpty = takeLock.newCondition();
    
    public ProxyResponseFilter rspFilter = new ProxyResponseFilter(responses,takeLock,notEmpty);

    public boolean loginFlag = false;
    
    
    public String execute(ImmutableMap<String,Object> param,String url){  
    	try {
    		String requsetId = WorkId.sortUID();
    		//确保请求和参数匹配
    		requsetLock.writeLock().lock(); 
        	requsets.add(Pair.of(requsetId, param)); 
    		driver.get(url); 
    		requsetLock.writeLock().unlock();
    		
			return poll(requsetId,268435456,10 * 1000 *1000 * 1000);
		} catch (Exception e){
			requsetLock.writeLock().unlock();
		} 
    	return "FAIL";
    }
    
    public String execute(ImmutableMap<String,Object> param,By javaScriptByXpath){
    	try {
    		String requsetId = WorkId.sortUID();
    		requsetLock.writeLock().lock();
        	requsets.add(Pair.of(requsetId,param)); 
        	((JavascriptExecutor) driver).executeScript("arguments[0].click();", driver.findElement(javaScriptByXpath));
        	requsetLock.writeLock().unlock();
        	return poll(requsetId,268435456,10 * 1000 *1000 * 1000);
		} catch (Exception e){
			requsetLock.writeLock().unlock(); 
		}  
    	return "FAIL";
    }
    
    public String execute(By javaScriptByXpath){
    	try {
    		
        	((JavascriptExecutor) driver).executeScript("arguments[0].click();", driver.findElement(javaScriptByXpath));
        	
		} catch (Exception e){
			
		}
    	return "OK";
    }

    /**
     * 为了尽量减少等待时间 默认时间单位为纳秒   1秒 = 1000毫秒 = 1000000微妙 = 1000000000纳秒
     * timebacth 时间根据实际请求进行修改 线程以2的指数级进行休眠,直到timeout最接近的2的指数。
     * 例如 timebacth = 1 则线程休眠1纳秒 然后 2纳秒 4纳秒 8纳秒 直到获取数据或者达到timeout
     * 极限情况是 timebacth = 1  timeout=9223372036854775807 则线程大约会等待212天
     * 
     * **/
    public String poll(String key,long timebacth,long timeout) throws InterruptedException {
    	String x = null;
        long nanos = powerOfTwo(TimeUnit.NANOSECONDS.toNanos(timeout));
        long bacthNanos = powerOfTwo(TimeUnit.NANOSECONDS.toNanos(timebacth));
        final ReentrantLock takeLock = this.takeLock;
        takeLock.lockInterruptibly();
        try {
           while (!responses.containsKey(key)) {
            	if (bacthNanos > nanos){
            		log.debug("请求无响应"); 
                	return "FAIL";
                } 
            	log.debug(Thread.currentThread().getName()+":"+key+"等待时间："+(bacthNanos));
                notEmpty.awaitNanos(bacthNanos);
                bacthNanos <<= 1;
            } 
            x = responses.get(key);
            if(StringUtils.isNotBlank(x)){
            	responses.remove(key);
            }else{
            	x = "FAIL";
            } 
        } finally {
            takeLock.unlock(); 
        } 
        return x;
    }
    /**
     * Returns a power of two size for the given target capacity 
     */
    private long powerOfTwo(long cap){
		long n = cap - 1;
	    n |= n >>> 1;
	    n |= n >>> 2;
	    n |= n >>> 4;
	    n |= n >>> 8;
	    n |= n >>> 16;
	    n |= n >>> 32;
	    return (n < 0) ? 1 : (n >= Long.MAX_VALUE) ? Long.MAX_VALUE : n + 1; 
	}
    
    private long heartbeatTime; 
    //固定执行时间，时间单位为毫秒,读者可自行设定，不得小于等于0
    private static Integer cacheTime = 14400000;
    //开始后延迟时间，时间单位为毫秒,读者可自行设定，不得小于等于0
    private static Integer delay = 1000;
   
    private void heartBeat(){
    	 Timer timer = new Timer();
         timer.schedule(new TimerTask() {
        	 @Override
        	 public void run(){
        		// System.currentTimeMillis() - heartbeatTime > 1000
           	 }
         }, delay, cacheTime);
    } 
    
    @PostConstruct
    public void toInit(){
    	proxy.start(8180); 
 	    Proxy seleniumProxy = ClientUtil.createSeleniumProxy(proxy);
	    ChromeOptions options = new ChromeOptions();
	    options.setCapability(CapabilityType.PROXY, seleniumProxy);
	    options.addArguments("ignore-certificate-errors");
	    options.addArguments("start-maximized");
    	System.setProperty("webdriver.chrome.driver",driverPath);
    	options.addArguments("headless"); //无界面参数
    	options.addArguments("disable-gpu"); 
    	options.addArguments("no-sandbox"); //禁用沙盒 就是被这个参数搞了一天
    	chromeService = new ChromeDriverService.Builder().usingDriverExecutable(new File(driverPath)).usingPort(3333).build();
    	try {
			chromeService.start();
		} catch (IOException e) { 
			e.printStackTrace();
		} 
    	driver = new ChromeDriver(chromeService,options);
    	//隐式等待,全局起作用
    	driver.manage().timeouts().implicitlyWait(10, TimeUnit.SECONDS);
    	proxy.enableHarCaptureTypes(CaptureType.REQUEST_CONTENT, CaptureType.RESPONSE_CONTENT); 
 	    proxy.setBlacklist(ImmutableSet.of(new BlacklistEntry("google.com", 404),
    			new BlacklistEntry("gstatic.com", 404),
    			new BlacklistEntry("clients4.google.com", 404),
    			new BlacklistEntry("beacons.gcp.gvt2.com", 404),
    			new BlacklistEntry("play.google.com", 404),
    			new BlacklistEntry("clientservices.googleapis.com", 404),
    			new BlacklistEntry("chrome-devtools-frontend.appspot.com", 404),
    			new BlacklistEntry("update.googleapis.com", 404))); 
    	proxy.addRequestFilter(reqFilter); 
    	proxy.addLastHttpFilterFactory(new ResponseFilterAdapter.FilterSource(rspFilter,20971520));
    	proxy.addHeader("Accept-Charset", "utf-8");
    } 
    
    class ProxyRequestFilter implements RequestFilter {
    	
    	public ProxyRequestFilter(LinkedBlockingQueue<Pair<String,ImmutableMap<String,Object>>> requsets){
    		this.requsets =requsets; 
    	}
    	final LinkedBlockingQueue<Pair<String,ImmutableMap<String,Object>>> requsets; 
    	 
    	List<String> valid = Lists.newArrayList("play.google.com", "accounts.google.com", "gstatic.com",
    			"clients4.google.com", "clientservices.googleapis.com", "chrome-devtools-frontend.appspot.com",
    			"update.googleapis.com", "beacons.gcp.gvt2.com"); 
    	  
    	@Override
    	public HttpResponse filterRequest(HttpRequest request, HttpMessageContents contents, HttpMessageInfo messageInfo) {
    		 
    	    ResourceLeakDetector.setLevel(ResourceLeakDetector.Level.ADVANCED);
			String url = request.uri();
			if (valid.contains(url)) {
				return new DefaultFullHttpResponse(request.protocolVersion(), HttpResponseStatus.FOUND);
			} 
			Pair<String,ImmutableMap<String,Object>> kv = requsets.poll(); 
			if(kv != null){
				messageInfo.getOriginalRequest().headers().add("requestId", kv.getKey());
				log.debug(kv.getKey()+ ":proxy发送请求：" +url);
				request.headers().remove("t");
				request.headers().remove("x");
				request.headers().add("t", kv.getValue().get("key1"));
				request.headers().add("x", kv.getValue().get("key2"));
				contents.setTextContents(String.valueOf(kv.getValue().get("key3"))); 
			} 	 
    		 
    		return null;
    	} 
    } 
    class ProxyResponseFilter implements ResponseFilter{
    	
    	public ProxyResponseFilter(ConcurrentHashMap<String, String> responses,
    			ReentrantLock takeLock,Condition notEmpty){
    		this.responses = responses;
    		//this.takeLock = takeLock;
    		//this.notEmpty = notEmpty;
    	}
    	private final ConcurrentHashMap<String, String> responses;
    	//private final ReentrantLock takeLock ;
    	//private final Condition notEmpty ;
    	
    	@Override
    	public void filterResponse(HttpResponse response, HttpMessageContents contents, HttpMessageInfo messageInfo) {
    	    ResourceLeakDetector.setLevel(ResourceLeakDetector.Level.ADVANCED);
    		String rquestId = messageInfo.getOriginalRequest().headers().get("requestId");
			if(StringUtils.isNotBlank(rquestId)){ 
				
				if(response.status().code() == HttpResponseStatus.OK.code()){
					 
					String reslut = contents.getTextContents();
					log.debug("响应："+rquestId+"=="+(StringUtils.isBlank(reslut)?"NULL":reslut));
					//store.add((StringUtils.isBlank(reslut)?"NULL":reslut));
					responses.put(rquestId, StringUtils.isBlank(reslut)?"NULL":reslut);
				}else{
					//store.add("FAIL");
					log.debug("响应："+rquestId+"==FAIL");
					responses.put(rquestId, "FAIL");
				} 
				
				/*  这种方式不行，在多线程的时候引起netty的 LEAK: ByteBuf.release() was not called before it's garbage-coll
				*        错误  目前不知道哪里的问题。
				 * try {
		            // 加锁
					takeLock.lockInterruptibly();
					notEmpty.signalAll();
		        }  catch (InterruptedException e) {
					e.printStackTrace();
				} finally {
		            // 释放锁
		        	takeLock.unlock(); 
		        } */
			}
    	} 
    }
}
